package vn.luongvo.widget.iosswitchview;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.RadialShader;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

/**
 * @author luongvo
 */

public class SwitchView extends ComponentContainer {

    private final int DEFAULT_COLOR_ON = 0xff53d769;
    private final int DEFAULT_COLOR_OFF = 0xffe3e3e3;

    private int colorOn = DEFAULT_COLOR_ON;
    private int colorOff = DEFAULT_COLOR_OFF;

    private final Paint paint = new Paint();
    private final Path sPath = new Path();
    private final Path bPath = new Path();
    private final RectFloat bRectF = new RectFloat();
    private float sAnim, bAnim;
    private RadialShader shadowGradient;
    private final AccelerateInterpolator aInterpolator = new AccelerateInterpolator(2);

    /**
     * state switch on
     */
    public static final int STATE_SWITCH_ON = 4;
    /**
     * state prepare to off
     */
    public static final int STATE_SWITCH_ON2 = 3;
    /**
     * state prepare to on
     */
    public static final int STATE_SWITCH_OFF2 = 2;
    /**
     * state switch off
     */
    public static final int STATE_SWITCH_OFF = 1;
    /**
     * current state
     */
    private int state = STATE_SWITCH_OFF;
    /**
     * last state
     */
    private int lastState = state;

    private boolean isChecked = false;

    private int mWidth, mHeight;
    private float sWidth, sHeight;
    private float sLeft, sTop, sRight, sBottom;
    private float sCenterX, sCenterY;
    private float sScale;

    private float bOffset;
    private float bRadius, bStrokeWidth;
    private float bWidth;
    private float bLeft, bTop, bRight, bBottom;
    private float bOnLeftX, bOn2LeftX, bOff2LeftX, bOffLeftX;

    private float shadowHeight;

    private OnCheckedChangeListener onCheckedChangeListener;

    private final EventHandler mEventHandler = new EventHandler(EventRunner.getMainEventRunner());

    public SwitchView(Context context) {
        this(context, null);
        init(null);
    }

    public SwitchView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public SwitchView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private void init(AttrSet attrs) {
        if (attrs != null){
            attrs.getAttr("color_on").ifPresent(attr -> colorOn = attr.getColorValue().getValue());
            attrs.getAttr("color_off").ifPresent(attr -> colorOff = attr.getColorValue().getValue());
            attrs.getAttr("checked").ifPresent(attr -> isChecked = attr.getBoolValue());
        }
        state = isChecked ? STATE_SWITCH_ON : STATE_SWITCH_OFF;
        setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                if ((state == STATE_SWITCH_ON || state == STATE_SWITCH_OFF) && (sAnim * bAnim == 0)) {
                    switch (touchEvent.getAction()) {
                        case TouchEvent.PRIMARY_POINT_DOWN:
                            return true;
                        case TouchEvent.PRIMARY_POINT_UP:
                            lastState = state;
                            if (state == STATE_SWITCH_OFF) {
                                refreshState(STATE_SWITCH_OFF2);
                            } else if (state == STATE_SWITCH_ON) {
                                refreshState(STATE_SWITCH_ON2);
                            }
                            bAnim = 1;
                            invalidate();

                            if (state == STATE_SWITCH_OFF2) {
                                toggle(STATE_SWITCH_ON);
                            } else if (state == STATE_SWITCH_ON2) {
                                toggle(STATE_SWITCH_OFF);
                            }

                            if (onCheckedChangeListener != null) {
                                onCheckedChangeListener.onCheckedChanged(SwitchView.this, isChecked);
                            }
                            break;
                    }
                }
                return false;
            }
        });
        setEstimateSizeListener((i,i1) -> {
            int w = EstimateSpec.getSize(i);
            int h = (int) (w * 0.65f);
            setEstimatedSize(w,h);

            onSizeChanged(w,h);
            return true;
        });
        addDrawTask();

        setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {

            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
                mEventHandler.removeTask(mRunnable);
            }
        });

    }

    private void addDrawTask() {
        addDrawTask(new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                ondraw(canvas);
            }
        });
    }


    private void onSizeChanged(int w, int h) {

        mWidth = w;
        mHeight = h;

        sLeft = sTop = 0;
        sRight = mWidth;
        sBottom = mHeight * 0.91f;
        sWidth = sRight - sLeft;
        sHeight = sBottom - sTop;
        sCenterX = (sRight + sLeft) / 2;
        sCenterY = (sBottom + sTop) / 2;

        shadowHeight = mHeight - sBottom;

        bLeft = bTop = 0;
        bRight = bBottom = sBottom;
        bWidth = bRight - bLeft;
        final float halfHeightOfS = (sBottom - sTop) / 2;
        bRadius = halfHeightOfS * 0.95f;
        bOffset = bRadius * 0.2f;
        bStrokeWidth = (halfHeightOfS - bRadius) * 2;

        bOnLeftX = sWidth - bWidth;
        bOn2LeftX = bOnLeftX - bOffset;
        bOffLeftX = 0;
        bOff2LeftX = 0;

        sScale = 1 - bStrokeWidth / sHeight;

        RectFloat sRectF = new RectFloat(sLeft, sTop, sBottom, sBottom);
        sPath.arcTo(sRectF, 90, 180);
        sRectF.left = sRight - sBottom;
        sRectF.right = sRight;
        sPath.arcTo(sRectF, 270, 180);
        sPath.close();

        bRectF.left = bLeft;
        bRectF.right = bRight;
        bRectF.top = bTop + bStrokeWidth / 2;
        bRectF.bottom = bBottom - bStrokeWidth / 2;
        Point point = new Point(bWidth / 2, bWidth / 2);
        float newRadius = bWidth / 2;
        float[] newStops = new float[1];
        final Color[] newColors = new Color[]{new Color(0xFF000000), new Color(0x00000000)};
        shadowGradient = new RadialShader(point, newRadius, newStops, newColors, RadialShader.TileMode.CLAMP_TILEMODE);
    }

    private void calcBPath(float percent) {
        bPath.reset();
        bRectF.left = bLeft + bStrokeWidth / 2;
        bRectF.right = bRight - bStrokeWidth / 2;
        bPath.arcTo(bRectF, 90, 180);
        bRectF.left = bLeft + percent * bOffset + bStrokeWidth / 2;
        bRectF.right = bRight + percent * bOffset - bStrokeWidth / 2;
        bPath.arcTo(bRectF, 270, 180);
        bPath.close();
    }

    private float calcBTranslate(float percent) {
        float result = 0;
        int switchState = state - lastState;
        switch (switchState) {
            case 1:
                // off -> off2
                if (state == STATE_SWITCH_OFF2) {
                    result = bOff2LeftX - (bOff2LeftX - bOffLeftX) * percent;
                }
                // on2 -> on
                else if (state == STATE_SWITCH_ON) {
                    result = bOnLeftX - (bOnLeftX - bOn2LeftX) * percent;
                }
                break;
            case 2:
                // off2 -> on
                if (state == STATE_SWITCH_ON) {
                    result = bOnLeftX - (bOnLeftX - bOff2LeftX) * percent;
                }
                // off -> on2
                else if (state == STATE_SWITCH_ON) {
                    result = bOn2LeftX - (bOn2LeftX - bOffLeftX) * percent;
                }
                break;
            case 3: // off -> on
                result = bOnLeftX - (bOnLeftX - bOffLeftX) * percent;
                break;
            case -1:
                // on -> on2
                if (state == STATE_SWITCH_ON2) {
                    result = bOn2LeftX + (bOnLeftX - bOn2LeftX) * percent;
                }
                // off2 -> off
                else if (state == STATE_SWITCH_OFF) {
                    result = bOffLeftX + (bOff2LeftX - bOffLeftX) * percent;
                }
                break;
            case -2:
                // on2 -> off
                if (state == STATE_SWITCH_OFF) {
                    result = bOffLeftX + (bOn2LeftX - bOffLeftX) * percent;
                }
                // on -> off2
                else if (state == STATE_SWITCH_OFF2) {
                    result = bOff2LeftX + (bOnLeftX - bOff2LeftX) * percent;
                }
                break;
            case -3: // on -> off
                result = bOffLeftX + (bOnLeftX - bOffLeftX) * percent;
                break;
        }

        return result - bOffLeftX;
    }


    private void ondraw(Canvas canvas) {

        paint.setAntiAlias(true);
        final boolean isChecked = (state == STATE_SWITCH_ON || state == STATE_SWITCH_ON2);
        // draw background
        paint.setStyle(Paint.Style.FILL_STYLE);
        paint.setColor(isChecked ? new Color(colorOn) : new Color(colorOff));
        canvas.drawPath(sPath, paint);

        sAnim = sAnim - 0.1f > 0 ? sAnim - 0.1f : 0;
        bAnim = bAnim - 0.1f > 0 ? bAnim - 0.1f : 0;

        final float dsAnim = aInterpolator.getInterpolation(sAnim);
        final float dbAnim = aInterpolator.getInterpolation(bAnim);

        // draw background animation
        final float scale = sScale * (isChecked ? dsAnim : 1 - dsAnim);
        final float scaleOffset = (bOnLeftX + bRadius - sCenterX) * (isChecked ? 1 - dsAnim : dsAnim);
        canvas.save();
        canvas.scale(scale, scale, sCenterX + scaleOffset, sCenterY);
        paint.setColor(new Color(0xffffffff));
        canvas.drawPath(sPath, paint);
        canvas.restore();
        // draw center bar
        canvas.save();
        canvas.translate(calcBTranslate(dbAnim), shadowHeight);
        final boolean isState2 = (state == STATE_SWITCH_ON2 || state == STATE_SWITCH_OFF2);
        calcBPath(isState2 ? 1 - dbAnim : dbAnim);
        // draw shadow
        paint.setStyle(Paint.Style.FILL_STYLE);
        paint.setColor(new Color(0xff333333));
        paint.setShader(shadowGradient, Paint.ShaderType.RADIAL_SHADER);
        canvas.drawPath(bPath, paint);
        paint.setShader(null, Paint.ShaderType.RADIAL_SHADER);
        canvas.translate(0, -shadowHeight);

        canvas.scale(0.98f, 0.98f, bWidth / 2, bWidth / 2);
        paint.setStyle(Paint.Style.FILL_STYLE);
        paint.setColor(new Color(0xffffffff));
        canvas.drawPath(bPath, paint);

        paint.setStyle(Paint.Style.STROKE_STYLE);
        paint.setStrokeWidth(bStrokeWidth * 0.5f);

        paint.setColor(isChecked ? new Color(colorOn) : new Color(colorOff));
        canvas.drawPath(bPath, paint);

        canvas.restore();

        paint.reset();
        if (sAnim > 0 || bAnim > 0){
           invalidate();
        }
    }

    private void refreshState(int newState) {
        if (!isChecked && newState == STATE_SWITCH_ON) {
            isChecked = true;
        } else if (isChecked && newState == STATE_SWITCH_OFF) {
            isChecked = false;
        }
        lastState = state;
        state = newState;
        invalidate();
    }

    /**
     * isChecked
     *
     * @return the state of switch view
     */
    public boolean isChecked() {
        return isChecked;
    }

    /**
     * if set true , the state change to on;
     * if set false, the state change to off
     *
     * @param isChecked
     */
    public void setChecked(boolean isChecked) {
        refreshState(isChecked ? STATE_SWITCH_ON : STATE_SWITCH_OFF);
    }

    /**
     * if set true , the state change to on;
     * if set false, the state change to off
     * <br><b>change state with animation</b>
     *
     * @param isChecked
     */
    public void toggle(final boolean isChecked) {
        this.isChecked = isChecked;
        mEventHandler.postTask(mRunnable,300);
    }
    private final Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            toggle(isChecked ? STATE_SWITCH_ON : STATE_SWITCH_OFF);
        }
    };

    private synchronized void toggle(int switchState) {
        if (switchState == STATE_SWITCH_ON || switchState == STATE_SWITCH_OFF) {
            if ((switchState == STATE_SWITCH_ON && (lastState == STATE_SWITCH_OFF || lastState == STATE_SWITCH_OFF2))
                    || (switchState == STATE_SWITCH_OFF && (lastState == STATE_SWITCH_ON || lastState == STATE_SWITCH_ON2))) {
                sAnim = 1;
            }
            bAnim = 1;
            refreshState(switchState);
        }
    }

    public interface OnCheckedChangeListener {
        void onCheckedChanged(SwitchView switchView, boolean isChecked);
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        this.onCheckedChangeListener = listener;
    }

}